package de.mmis.core.base.infrastructure;

import java.net.InetAddress;
import java.net.UnknownHostException;

import de.mmis.core.base.Annotations.NoProperty;
import de.mmis.core.base.DeserializableAnnotations.ConstructorDeserializable;
import de.mmis.core.base.DeserializableAnnotations.DeserializationConstructor;
import de.mmis.core.base.MethodDescriptor.P;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.event.AbstractTypedEvent;

@ConstructorDeserializable(typeIdentifier = "infrastructure-event")
public class InfrastructureEvent extends
		AbstractTypedEvent<InfrastructureEvent.InfrastructureEventType> {

	public static enum InfrastructureEventType {
		/** The device is stored in device */
		NEW_DEVICE,

		/**
		 * When you receive this event, the device has been removed from the
		 * infrastructure. Note that, due to connection loss, not every device
		 * removal generates this event. But if a connection loss is detected,
		 * clean is set to false.
		 */
		DEVICE_REMOVED,

		/**
		 * the response of a search device - the device is stored in device
		 */
		ANSWER_DEVICE,

		/**
		 * this event is generated when a complete new machine sets up its
		 * infrastructure. The field address contains the source address of the
		 * message.
		 */
		NEW_MACHINE,

		/**
		 * this event is generated when any infrastructure server has moved to
		 * another VM on the same machine. The field address contains the source
		 * address of the message.<br/>
		 * If the old VM had hosted devices, you won't get any
		 * {@link #DEVICE_REMOVED} messages, so you may want to query all still
		 * existing devices. But note that the new server may not know all local
		 * devices immediately after this event is received.<br/>
		 * If the old server announced it will no longer run the infrastructure,
		 * clean is set to true, otherwise to false.
		 */
		MIGRATED,

		/**
		 * this event is generated when any machine announced it will stop. The
		 * field address contains the source address of the message.<br/>
		 * Note that another VM on the same machine may set up a new server. In
		 * this case, you will get an additional {@link #MIGRATED} event.
		 */
		SHUT_DOWN_ANNOUNCEMENT,

		/**
		 * the local infrastructure is not running any longer.<br/>
		 * the field exception may be set.
		 */
		SHUT_DOWN,

		/**
		 * Some lifesign from a host, {@link InfrastructureEvent#address}
		 * contains the address of the host
		 */
		HEARTBEAT
	}

	/**
	 * if this Event contains any device, it's stored here
	 */
	public PublishedDeviceDescription device;

	/**
	 * this field may be set on a {@link InfrastructureEventType#SHUT_DOWN
	 * SHUT_DOWN} event indicating the cause of the shut down.
	 */
	public Exception exception;

	/**
	 * this field is set on a {@link InfrastructureEventType#MIGRATED MIGRATED},
	 * {@link InfrastructureEventType#NEW_MACHINE NEW_MACHINE} and
	 * {@link InfrastructureEventType#SHUT_DOWN_ANNOUNCEMENT
	 * SHUT_DOWN_ANNOUNCEMENT} event and contains source address of the original
	 * message received by the infrastructure.<br/>
	 * It's also set on a {@link InfrastructureEventType#HEARTBEAT HEARTBEAT}
	 * event
	 */
	public InetAddress address;

	/**
	 * indicates whether one of the following events was generated due to an
	 * error (like connection loss, clean = false) or because this was planned
	 * (clean = true).
	 * 
	 * <ul>
	 * <li>{@link InfrastructureEventType#DEVICE_REMOVED DEVICE_REMOVED}</li>
	 * <li>{@link InfrastructureEventType#MIGRATED MIGRATED}</li>
	 * <li>{@link InfrastructureEventType#SHUT_DOWN SHUT_DOWN}</li>
	 * </ul>
	 */
	public boolean clean;

	public InfrastructureEvent(InfrastructureEventType type) {
		super(type);
	}

	@DeserializationConstructor
	public InfrastructureEvent(@P("EventType") InfrastructureEventType type,
			@P("Device") PublishedDeviceDescription device,
			@P("Exception") Exception exception, @P("AddressS") String address,
			@P("Clean") boolean clean) {
		this(type);
		this.device = device;
		this.exception = exception;
		setAddressS(address);
		this.clean = clean;
	}

	public PublishedDeviceDescription getDevice() {
		return device;
	}

	public Exception getException() {
		return exception;
	}

	@NoProperty
	public InetAddress getAddress() {
		return address;
	}

	/**
	 * @return {@link #getAddress()} as a String
	 */
	public String getAddressS() {
		if (address == null)
			return null;
		return address.getHostAddress();
	}

	private void setAddressS(String address) {
		if (address == null) {
			this.address = null;
			return;
		}
		try {
			this.address = InetAddress.getByName(address);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}

	public boolean isClean() {
		return clean;
	}

	@Override
	public String toString() {
		String s = getEventType().toString();

		switch (getEventType()) {
		case DEVICE_REMOVED:
		case ANSWER_DEVICE:
		case NEW_DEVICE:
			s += " " + Tree.fromObject(device, true).toString();
			break;
		case SHUT_DOWN:
			if (this.exception != null)
				s += " " + exception.toString();
			break;
		case NEW_MACHINE:
		case MIGRATED:
		case SHUT_DOWN_ANNOUNCEMENT:
		case HEARTBEAT:
			if (address != null)
				s += " " + address.toString();
			break;
		}

		switch (getEventType()) {
		case DEVICE_REMOVED:
		case MIGRATED:
			if (this.clean)
				s += " clean";
			else
				s += " dirty";
			break;

		default:
			break;
		}

		return s;
	}
}
